#include <gccore.h>
#include <unistd.h>
#include <assert.h>

#include <mxml.h>
#include <zlib.h>

#include <debug.h>

#include <grrlib.h>

#include "gecko_debug.h"
#include "map.h"

static mxml_node_t *tree = NULL;
//static mxml_node_t *current = NULL;
static mxml_node_t *item = NULL;

mxml_node_t *LoadXMLNode(const char *name, const char *attr, const char *value)
{
	// searches from top, puts node in 'item'
	item = mxmlFindElement(tree, tree, name, attr, value, MXML_DESCEND);
	if(!item) {
		// ERROR
		debug(OUT, "Error reading xml node (name, attr, value): %s, %s, %s.", name, attr, value);
		return NULL;
	}
	
	return item;
}

/* Trim whitespace from start/end of string.
 *  returns new string and length
 *
 * removes: 0x20, 0x0a, '\t'
 */
void TrimWhitespace(char *src, char **dest, int *destLen)
{
	// remove whitespace from end first
	int i;
	for (i=strlen(src)-1; i>0; i--)
	{
		if (src[i] != '\x20' && src[i] != '\x0a' && src[i] != '\t')
			break;
	}
	src[i+1] = '\0';
	
	// remove whitespace from beginning
	for (i=0; src[i] != '\0'; i++)
	{
		if (src[i] != '\x20' && src[i] != '\x0a' && src[i] != '\t')
			break;
	}
	// return new string
	int newLen = strlen((const char*)&src[i]);
	char *newString = (char*)malloc(newLen);
	strcpy(newString, (const char*)&src[i]);
	
	*destLen = newLen;
	*dest = newString;
}

/* Decompress from source buffer (src) and provided length (src_len).
    The result is returned in (dest), with length given by (dest_len)*/
#define CHUNK 2048
int Extract(char* src, unsigned int src_len, void **dest, unsigned int *dest_len)
{
    int ret;
    unsigned have;
    z_stream strm;
    //unsigned char in[CHUNK];
    //unsigned char out[CHUNK];
	
	// inflation helpers
	char *out = NULL;
	unsigned curr_dest_size;

    /* allocate inflate state */
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = src_len;
    strm.next_in = Z_NULL;
    ret = inflateInit(&strm);
    if (ret != Z_OK)
        return ret;
		
	// pre-set outputs
	*dest_len = 0;
	*dest = NULL;
		
	// prepare first inflation
	strm.next_in = (Bytef*)src;
	
	debug(OUT, "Inflating.\n");	// DEBUG
	
	out = (char*)malloc(1);
	curr_dest_size = 0;

	/* run inflate() on input until there is none left */
	do {
		// allocate some more output space
		curr_dest_size += CHUNK;
		out = (char*)realloc(out, curr_dest_size);
		
		debug(OUT, "curr_dest_size = %u\n", curr_dest_size);	// DEBUG
		
		// inflate
		strm.avail_out = CHUNK;
		strm.next_out = (Bytef*) (out + curr_dest_size - CHUNK);
		ret = inflate(&strm, Z_NO_FLUSH);
		assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
		switch (ret) {
			case Z_NEED_DICT:
				ret = Z_DATA_ERROR;     /* and fall through */
			case Z_DATA_ERROR:
			case Z_MEM_ERROR:
				(void)inflateEnd(&strm);
				return ret;
		}
	} while (ret != Z_STREAM_END);
	
	// resize output buffer to exactly what
	// we have.
	have = curr_dest_size - strm.avail_out;
	out = (char*)realloc(out, have);
	
    /* clean up and return */
    (void)inflateEnd(&strm);
	*dest = out;
	*dest_len = have;
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}

Map::Map()
{

}

Map::~Map()
{

}

// load a tmx map file
//
// path: url to .tmx xml file
void Map::Load(char *path)
{
	//
	// open tmx file
	//
	FILE *fp = NULL;

    fp = fopen(path, "r");
	assert (fp != NULL);
	
    tree = mxmlLoadFile(NULL, fp, MXML_OPAQUE_CALLBACK);
	assert(tree != NULL);
	
	/***
	<map version="1.0" orientation="orthogonal" width="28" height="16" tilewidth="32" tileheight="32">
	 <tileset name="tileset_one" firstgid="1" tilewidth="32" tileheight="32">
	  <image source="tileset_one.gif"/>
	 </tileset>
	 <layer name="Layer 0" width="64" height="64" x="-8" y="-3">
	  <data encoding="base64" compression="gzip">
	   COMPRESSED DATA HERE
	  </data>
	 </layer>
	</map>
	***/
	//
	// parse xml and update map object
	//
	mxml_node_t	*node;
	
	// <map>
	node = mxmlFindElement(tree, tree, "map", NULL, NULL, MXML_DESCEND);
	if (node != NULL)
	{
		//mxmlElementGetAttr(node, "orientation");
		map_w = atoi(mxmlElementGetAttr(node, "width"));
		map_h = atoi(mxmlElementGetAttr(node, "height"));
		tile_w = atoi(mxmlElementGetAttr(node, "tilewidth"));
		tile_h = atoi(mxmlElementGetAttr(node, "tileheight"));
	}
	
	// <tileset>
	node = mxmlFindElement(tree, tree, "tileset", NULL, NULL, MXML_DESCEND);
	if (node != NULL)
	{
		//mxmlElementGetAttr(node, "name");
		first_tile = atoi(mxmlElementGetAttr(node, "firstgid")) - 1;
	}
	
	// <image>
	node = mxmlFindElement(tree, tree, "image", NULL, NULL, MXML_DESCEND);
	if (node != NULL)
	{
		tileset_path = (char*)mxmlElementGetAttr(node, "source");
	}
	
	// <layer> Layer 0
	node = mxmlFindElement(tree, tree, "layer", "name", "Layer 0", MXML_DESCEND);
	if (node != NULL)
	{
		//mxmlElementGetAttr(node, "name");
		//mxmlElementGetAttr(node, "width");
		//mxmlElementGetAttr(node, "height");
	}
	// <data> (from current node)
	node = mxmlFindElement(node, tree, "data", NULL, NULL, MXML_DESCEND);
	if (node != NULL)
	{
	
		/*
		//mxmlElementGetAttr(node, "encoding");
		//mxmlElementGetAttr(node, "compression");
		// get value of this node
		//node.value.text.string	// char* - strip whitespace?
		
		//
		// decompress map data (gzipped)
		//
		char* srcData;
		int srcDataLen;
		// strip whitespace, line feeds (0x20, 0x0a)
		TrimWhitespace((char*)node->child->value.custom.data, &srcData, &srcDataLen);

		int res = Extract(srcData, srcDataLen, &map_data, &map_data_len);
		assert (res == Z_OK);
		*/
		struct mxml_node_s *cnode = node->child;
		int i;
		map_data = calloc(map_w*map_h, sizeof(int));	// allocate map array: int[row][col]
		for (i = 0; i < map_w*map_h; i++)
		{
			// read in map values
			((int*)map_data)[i] = atoi(mxmlElementGetAttr(cnode, "gid"));
			// move to the next node
			cnode = cnode->next;
		}
	}
	
	//
	// Construct Map Representation (array?)
	//
	debug(OUT, "Resulting map data:\n");
	for(int i=0; i < map_w * map_h; i++)
		debug(OUT, "%d ", ((int*)map_data)[i]);
		
	//
	// Read Tileset Image
	//
	tex_tileset = GRRLIB_LoadTextureFromFile(tileset_path);	// load tileset image from file
	
	GRRLIB_InitTileSet(tex_tileset, tile_w, tile_h, first_tile);
	
	// return
	fclose(fp);
}
